/**
 * IPTV机顶盒消息推送系统客户端JS. 
 *
 * 本JS负责与后端消息系统建立“连接”，并将收到的消息通过回调函数通知页面。
 *
 * 代码示例：
 *
 * var msgpusher = new MsgPusher();
 
 * //初始化
 
 * msgpusher.setOptions({

 * 		epg:"defaultsmchd",//epg版本信息，与epg发布目录一致。详见《消息推送接口规范》。

		userId: "088820140113088",//用户真实的业务帐号，Authentication.CTCGetConfig("UserID")
 *
		resources: "2201",//如果是直播界面，这是直播频道id；如果是“首页”，请填“home”；其它页面待定
 *
		restype: 1,//定义resources的类型，1：直播频道号，2：点播视频id，3：EPG页面
 *
 		mediaPlayer: mp,//
 *		
		stbType: Authentication.CTCGetConfig("STBType"),//机顶盒型号。后台需要根据不同型号的盒子采取不同优化策略。填错有可能收不到消息。
 *
		hosts: ["14.29.1.174"],//这是体验环境消息服务器地址，正式上线请改为生产服务器地址。
 *
		epgSession: /JSESSIONID=(.*?)(;|$)/.exec(document.cookie)[ 1 ]//用于验证请求是否来自EPG，否则认为是恶意请求。其原理是获取epg分配给机顶盒的sessionid，后台将该sessionid发给epg服务器进行验证。
 
 * });
  
 * //设置消息处理回调函数，可以根据不同的appid设置相应的消息处理器：第二参数为appid，若不提供则接收所有消息。出于安全管控考虑，建议都设置appid参数。
 
 * msgpusher.registerHandler(function(message){
 
 * 	 msgpusher.remoteLog("handlerfrontappid2",message.id);//在handler的首行插入远程打点日志代码，以便监控消息是否有丢失。
 
 *   alert("应用2接收到消息："+JSON.stringify(message))//此处执行消息处理的业务逻辑
 
 * }, 2);

 * //建立消息连接 
 
 * msgpusher.connect();
 * 
 * // ...your own code here
 

 * //在切换频道时通知后台用户当前观看的频道。
 
 * //<b>注意！！！</b>每次频道切换的时候都要调用该接口，否则消息后台不知道用户当前在看哪个频道，无法推送针对频道广播的消息。
 *
 * msgpusher.updateResources(<new_channel_id>);
 *
 * @class
 */
function _ReadMe(){};
/**
* 
*/
_ReadMe.Welcome = {
	//这个空对象用于jsduck生成前置说明，免去单独维护welcome.html文档的麻烦。
};


function consoleLog() {
//	console.log.apply(console, arguments);
}

//支持apk
var JSVERSION="3_0_889";

/**
 * 资源类型
 * @enum
 */
MSG_RESOURCE_TYPE = {
		/**
		 * 1：直播频道号
		 * @type {number}
		 */
		 LIVE : 1,
		 /** 
		 	* 2：点播视频ID
		 	*/
		 VOD : 2,
		 /** 
		 	* 3：回看
		 	*/
		 PROG:3,
		 /** 4：EPG页面
		  */
		 PAGE : 4,
		 /** 
		  *5、延时播放（直播的延时播放，暂停再播，或跳转到之前的时间），注：暂停状态也算是时移
		  */
		 TIMESHIFT : 5
};

/**
 * 消息类型
 * @enum
 */
MSG_PUSHER_TYPE = {
	/**
	 * 群发消息
	 * @type {number}
	 * @property
	 */
	DEFAULT : 1,
	/**
	 * 点对点消息
	 * @type {number}
	 * @property
	 */
	P2P: 2,
	/**
	 * 预览消息
	 * @type {number}
	 * @property
	 */
	PREVIEW: 3,
	/**
	 * 控制消息
	 * @type {number}
	 * @property
	 */
	CONTROL: 4,
	/**
	 * 本地服务通知js到后台去获取消息
	 */
	NOTIFY:7
};

/**
 * 机顶盒消息推送客户端
 * @author wml
 * @class
 */
function MsgPusher() {
	//设置默认选项
	var defaultOptions = this.options;
	//不能直接使用this.options(等于MsgPusher.prototype.options)，他是所有实例公用的，所以这里重新建立一个options。
	this.options = {};
	this.setOptions(defaultOptions);
	
	this.hasError = 0;
	this.nextReqDelay = this.getOptions().interval;
	this.allocateHost();
	
	var that = this;
	MsgPusher.Util.addEvent(window, "unload", function() {
		if(that.options.restype == MSG_RESOURCE_TYPE.LIVE){
			that.uploadHua({resID:that.options.resources,nextChan:null});
		}
		that.disconnect();
	});
	MsgPusher.Util.addEvent(document, "visibilitychange", function() {//浏览器tab被切换
		  if("hidden" == document.visibilityState ){
			  if(that.longpollingAjax){
				  that._hiddenByUnvisible = true;
			  }
			  that.uploadHua({resID:that.options.resources,nextChan:null});
			  that.disconnect();
			  that.remoteLog("visibilitychange","unvisible",true);
		  }else{
			  if(that.options.restype == MSG_RESOURCE_TYPE.LIVE){
				  that.uploadHua({resID:that.options.resources});
			  }
			  that.reconnect();
			  that._hiddenByUnvisible = false;
			  that.remoteLog("visibilitychange","visible",true);
		  }
	});
}
MsgPusher.prototype = {
	/**
	 * 选项
	 * @property
	 */
	options: {
		/**
		 * {默认10} 长连接完成后重连的时间间隔（消息服务器会主动设置）
		 * @type {number}
		 * @property
		 */
		interval: 10,
		/**
		 * {默认500} 长连接出错后重连的时间间隔（会自动计算，连续出错次数越多值越大，收到正常请求后重置）
		 * @type {number}
		 * @property
		 */
		errorInterval: 500,
		/**
		 * 来源ID，由消息系统颁发给接入的系统。Sid与计算sig的key一一对应。由管理系统统一颁发。IPTV暂写死为1。
		 * @type {number}
		 * @property
		 */
		sid: 1,
		/**
		 * 用户id
		 * @type {string}
		 * @property
		 */
		userId: null,
		/**
		 * 消息资源（必须配合restype使用），表示消息将发送给订阅了该资源的所有用户。
		 * 例如一个直播频道：ch10
		 * 或一个点播视频ID或页面标识。
		 * @type {string}
		 * @property
		 */
		resources: null,
		/**
		 * 表示resource的资源类型
		 * @type {MSG_RESOURCE_TYPE}
		 * @property
		 */
		restype: 0,
		/**
		 * 播放器封装对象
		 * @type {object}
		 * @property
		 */
		 mediaPlayer:null,
		/**
		 * 消息服务器默认连接地址（域名或ip，可以带端口）
		 * @type {string}
		 * @property
		 */ 
		hosts: ["14.29.2.19","14.29.2.20"],
		/**
		 * EPG版本信息
		 * @type {string}
		 * @property
		 */
		epg: "defaultsmchd",
		/**
		 * 机顶盒型号
		 * @type {string}
		 * @property
		 */
		stbType: null,
		/**
		 * EPG的SessionID，用于验证请求是否由EPG发起
		 */
		epgSession: null,
		/**
		 * 消息处理器,function(msg){};的数组，可以有多个处理器。分发器将接收到的每个消息分发给所有已注册的handler，有handler决定是否处理。
		 * 此处注册的处理器会处理所有消息，如需根据appid(应用ID)处理特定消息请通过registerHandler注册处理器。
		 * @type {function[]}
		 * @property
		 */
		handlers: [],
		/**
		 * 限制只有指定类型机顶盒连接消息服务器
		 */
		//PASS_TYPE: ["BoxSimulator","EC2108B_pub","EC6108V8U_pub_gdydx_toEPG"],
		/**
		 * 区域id
		 */
		area: "",
		/**
		 * 用户分组
		 */
		userGroup:"",
	},
	
	longpollingAjax: null,		//长轮询的ajax对象，具体见MsgPusher.Util.ajaxSubmit，可以用来中断请求
	host: null,					//当前主机地址
	failedHosts: [],			//出错的主机列表
	_localHost: "127.0.0.1:21931",
	_localServiceOk: false,		//本地apk服务是否可用
	_isNotified:false,			//true表示本地apk通知js直接前往后台获取消息，从后台返回（不管成功还是失败）后马上充值为false
	_hiddenByUnvisible:false,
	_remoteLog:false,//是否发远程打点消息。可以由后台发指令变更该值。
	_vodMsgs:[],//点播互动的消息
	_vodPollingInterval:99999,//点播互动的轮询间隔，先去一个很大的值，然后根据所有消息的tolerance取比它们都小的一个值。
	_vodHandlers:[],//点播互动的handler
	_vodLastEventTime:-1,//点播互动上次触发播放状态下的定时消息的时间点
	_HUA_STAT:false,//华为采集开关，暂不打开。
	
	/**
	 * 分配host1
	 * @param {host} failedHost		出错的host
	 */
	allocateHost: function( failedHost ) {
		var hosts = this.options.hosts;
		var host = null;
		
		if(failedHost) {
			
			if(!this.contains( this.failedHosts, failedHost )) {
				this.failedHosts.push(failedHost);
			}
			
			//检查没在failedHosts里的host
			for (var i = 0; i < hosts.length; i++) {
				host = hosts[i];
				if( !this.contains( this.failedHosts, this.addPort(host) ) ) {
					break;
				} else {
					host = null;
				}
			}
			
			if(!host) {
				//如果全部失败，选第一个
				host = hosts[0];
				
				//下次失败时轮流获取
				this.failedHosts = [];
			}
			
		} else {
			host = hosts[ this.options.userId % hosts.length ];
			if(!host) {
				host = hosts[ Math.ceil(Math.random()*hosts.length) - 1 ];
			}
		}
		this.setHost(host);
	},
	contains: function(array, elm) {
		return this.indexOf(array, elm) > -1;
	},
	indexOf: function(array, elm) {
		for (var i = 0; i < array.length; i++) {
			if(array[i]===elm){
				return i;
			}
		}
		return -1;
	},
	addPort: function(host) {
		if(host.indexOf(":") == -1){
			host = host+":8888";
		}
		return host;
	},
	/**
	 * 设置host
	 */
	setHost: function(host) {
		this.host = this.addPort(host);
	},
	/**
	 * 判断当前机顶盒是否功能盒子
	 */
	isLinuxBox: function(){
		if(this.options.stbType){
			if(this.options.stbType.indexOf("EC2108")>=0){
				return true;
			}
			if(this.options.stbType.indexOf("B700")>=0){
				return true;
			}
			if(this.options.stbType.indexOf("DS4904")>=0){
				return true;
			}
			if(this.options.stbType.indexOf("HG650")>=0){
				return true;
			}
		}
		return false;
	},
	/**
	 * 设置选项，未给出的会使用默认值或上次设置的值,给出的值会覆盖原有的值。
	 */
	setOptions: function(options) {
		MsgPusher.Util.extend.call(this.options,options);
		if(!this.options.stbType && window.Authentication) {
			this.options.stbType = Authentication.CTCGetConfig("STBType");
		}
		if(this.options.hosts) {
			this.allocateHost();
		}
		switch(options.epg){
			case "defaulthdcctv":
			case "smarthomeszgx":
			case "defaultsdcctv":
				this._HUA_STAT = true;
				break;
			default:
				this._HUA_STAT = false;
		}
		if(typeof(options.mediaPlayer) == "object"){
			this.options.mediaPlayer = {
					getCurrentPlayTime:function(){
						try{
							var r = parseInt(options.mediaPlayer.getCurrentPlayTime());
							if(r>=0){//r 有可能是NaN
								return r;
							}else{
								return -1;
							}
						}catch(e){
							return -1;
						}
					},
					getPlaybackMode:function(){
						try{
							var str = options.mediaPlayer.getPlaybackMode();
							var mode = JSON.parse(str);
							if(typeof(mode) == "object"){
								return mode.PlayMode;
							}else{
								return null;
							}
						}catch(e){
							return null;
						}
					},
					getMediaDuration:function(){
						try{
							var r = parseInt(options.mediaPlayer.getMediaDuration());
							if(r>=0){
								return r;
							}else{
								return -1;
							}
						}catch(e){
							return -1;
						}
					},
			}
		}else{
			this.options.mediaPlayer = {
					getCurrentPlayTime:function(){return -1;},
					getPlaybackMode:function(){return null;},
					getMediaDuration:function(){return -1;}
				};
		}
		consoleLog("options change: "+JSON.stringify(options));
	},
	/**
	 * 获取选项
	 */
	getOptions: function() {
		return this.options;
	},
	/**
	 * 注册一个消息处理函数。所有注册的消息处理函数平等地获取到所有的消息。也可以在setOptions中设置。
	 * @param {function} handler 	function(msg){};这样的函数。
	 * @param {number} appid		应用ID，不设置则处理所有消息
	 */
	registerHandler:function(handler, appid){
		var _handler = function(msg){handler(msg);};
		if(arguments.length == 2) {
			_handler.appid = appid;
		}
		if(arguments.length == 2) {
			handler.appid = appid;
		}
		this.options.handlers = (this.options.handlers||[]);
		this.options.handlers.push(_handler);
	},
		
	/**
	 * 更新频道id或点播视频id或其他订阅资源号，
	 * 在10秒后通知后台更新订阅资源号（避免频繁换台时时发送无用请求）
	 * @param {string} resources 资源id
	 * @param {MSG_RESOURCE_TYPE}
	 */
	updateResources: function(resources,restype) {//由直播切换到时移播放时允许resources是undefined。即只更新restype，resources保持不变。
		if(this.options.resources != resources && undefined != resources) {
			if(this.options.restype == MSG_RESOURCE_TYPE.LIVE){
				this.uploadHua({resID:this.options.resources,nextChan:resources});
			}
			this.options.resources = resources;
		}
		if(typeof(restype) != "undefined"){
			this.options.restype = restype;
		}
		var that = this;
		clearTimeout(this.resourceChangeTimer);
		this.resourceChangeTimer = setTimeout(function(){
			if(that.longpollingAjax) {//连接了才上报
				that.uploadInfo();
			}else{//短连接，当前没连接，则马上连一下(服务器有可能根据频道变化信息将盒子变为长连接)。
				that.reconnect();
			}
		}, 10000);
	},
	/**
	*检查消息是否有效的点播互动消息
	*@param {object}msg 消息对象
	*@param {number} dur影片的时长,-1表示时长未知。
	*/
	isValidVodMessage:function(msg,dur){
		if(typeof(msg)!="object"){
			return false;
		}
		if(typeof(msg.shift)!="number"){
			return false;
		}
		if(dur > 0 && dur < msg.shift){//是shift不在dur范围内。
			return false;
		}
		if(dur + msg.shift<0){//获取不到dur，或shift是负数，但不在dur范围内。
			return false;
		}
		return true;
	},
	/**
	*点播事件轮询。即定期检查播放进度和状态。在合适时间弹出消息。
	*/
	vodPolling:function(){
		var that = this;
		var currTime = this.getOptions().mediaPlayer.getCurrentPlayTime();
		var currMode = this.getOptions().mediaPlayer.getPlaybackMode();
		consoleLog("currTime:"+currTime+", currMode:"+currMode);
		for(var i=0;i<this._vodMsgs.length;i++){
			var msg = this._vodMsgs[i];
			if(msg.status == currMode && msg.shift<=currTime && msg.shift+msg.tolerance > currTime){
				if(that._vodLastEventTime < msg.shift || that._vodLastEventTime >= msg.shift+msg.tolerance){//如果已经弹过消息，并且没离开过弹消息区域，就不再弹消息。
					for(var j=0;j<that._vodHandlers.length;j++){
						var handler = that._vodHandlers[j];
						try {
							handler(msg);
							this.remoteLog("aftervodhandler:appid:"+handler.appid, j);
						} catch (e) { // 为了不影响其他消息的处理。
							consoleLog("vodhandler error: " + e.message);
							this.remoteLog("errorvodhandler:appid:"+handler.appid, j);
						}
					}
					that._vodLastEventTime = currTime;
				}
			}
		}
		clearTimeout(this.vodPollingTimer);
		this.vodPollingTimer = setTimeout(function(){that.vodPolling();},this._vodPollingInterval*1000);
	},
	/**
	*启动点播互动
	*/
	startVodInteraction:function(){
			var that = this;
			var opt = this.getOptions();
			var data = "cmd=message&uid="+opt.userId
				+ "&sid="+opt.sid
				+ "&resource="+(opt.resources || "")
				+ "&progress="+opt.mediaPlayer.getCurrentPlayTime()
				+ "&restype=" + (opt.restype || "")
				+ "&epg="+encodeURIComponent(opt.epg)
				+ "&stbType=" + (opt.stbType || "")
				+ "&epgSession=" + (opt.epgSession || "")
				+ "&ts="+new Date().getTime()
				+ "&area=" + (opt.area || "")
				+ "&userGroup=" + (opt.userGroup || "");
			
		consoleLog("startVodInteraction:"+"http://"+this.host+"/vod.do",data);
			MsgPusher.Util.ajaxSubmit({
				url:"http://"+this.host+"/vod.do",
				data:data,
				success:function (result) {
					consoleLog("startVodInteraction success:"+JSON.stringify(result));
					//result = {res_code:0,res_desc:"success",message:{msgid:123,appid:300,msgtype:8,messages:[{shift:60,content:{id:1,ctn:"just test!"}},{shift:90},{shift:120,tolerance:20},{shift:-60,ctn:"dur-60"}]}};
					if(result.message && typeof(result.message.messages) == "string"){
						result.message.messages = JSON.parse(result.message.messages);
						consoleLog("parsed:"+JSON.stringify(result));
					}
					if(result.message && result.message.messages instanceof Array && result.message.messages.length>0){
						for(var j=0;j<that.getOptions().handlers.length;j++){
							var handler = that.getOptions().handlers[j];
							if(handler.appid == result.message.appid) {
								that._vodHandlers.push(handler);
							}
						}
						if(that._vodHandlers.length > 0){
							for(var i=0;i<result.message.messages.length;i++){//检查每个消息，把有效消息缓存下来。
								var msg = result.message.messages[i];
								var dur = that.getOptions().mediaPlayer.getMediaDuration();
								if(that.isValidVodMessage(msg,dur)){
									if(msg.shift<0){
										msg.shift = dur + msg.shift;
									}
									if(!msg.tolerance){
										msg.tolerance = 5;
									}
									if(!msg.status || msg.status=="PLAY"){
										msg.status = "Normal Play";
									}
									if(that._vodPollingInterval > msg.tolerance){
										that._vodPollingInterval = msg.tolerance;
									}
									that._vodMsgs.push(msg);
								}else{
									that.remoteLog("invalidvodmsg",i);
								}
							}
							if(that._vodMsgs.length>0){//根据所有消息的tolerance值确定最小轮询间隔。即比所有消息的tolerance至少小1秒，但不能小于1秒。然后触发轮询
								if(that._vodPollingInterval > 1){
									that._vodPollingInterval--;
								}
								//执行轮询，每次轮询检查是否要弹出消息。考虑到消息不会多，一般不会超过3条，所以不考虑算法优化，每次逐个比较即可。
								that.vodPolling();
							}
						}
					}
				},
				error:function(err){
					consoleLog("vod.do fail!"+err);
					that.remoteLog("vod.do fail!","vod.do");
				},
				timeout:2000
			});
	},
	/**
	 * 检查本地服务是否可用
	 * 本地服务存在则回到avail，否则回调unavail
	 */
	checkLocalService:function(avail,unavail){
		if(!this._localHost || this.isLinuxBox()){
			unavail();
		}else{
			MsgPusher.Util.ajaxSubmit({
				url:"http://"+this._localHost+"/memcache.do",
				data:"action=get&key=test&ts="+new Date().getTime(),
				success:function (result) {avail();},
				error:function(err){unavail();},
				timeout:2000
			});
		}		
	},
	/**
	 * 启动消息推送，会自动判断是重新连接还是新连接。
	 */
	connect: function() {
		var delay = 5000;
		var that = this;
		setTimeout(function(){
			if(that.getOptions().restype == MSG_RESOURCE_TYPE.VOD){
				that.startVodInteraction();
			}
			that.checkLocalService(
					function(){
						that._localServiceOk = true;
						that.getOptions().sid = 2;
						that.setHost(that._localHost);
						that.reconnect();
						that.updateInfo();//apk不上报频道信息，第一次连接时需要主动上报。
					},
					function(){
						consoleLog("local service unavail!");
						var oldhost = MsgPusher.Util.localStore.get("msgpusher_host");
consoleLog("oldhost="+oldhost);
						if(null != oldhost){
consoleLog("use old host:"+oldhost);
							that.setHost(oldhost);
						}
						that.reconnect();
					});
		    if(that.options.restype == MSG_RESOURCE_TYPE.LIVE){
	          that.uploadHua({resID:that.options.resources});
	        }
		},delay);
	},
	/**
	 * 更新上报客户端变化
	 */
	uploadInfo: function() {
		var opt = this.getOptions();
		var url = "http://" + this.host + "/resource.do";
		var data = "uid="+opt.userId
			+ "&sid="+opt.sid
			+ "&resources="+(opt.resources || "")
			+ "&progress="+opt.mediaPlayer.getCurrentPlayTime()
			+ "&restype=" + (opt.restype || "")
			+ "&epg="+encodeURIComponent(opt.epg)
			+ "&stbType=" + (opt.stbType || "")
			+ "&epgSession=" + (opt.epgSession || "")
			+ "&area=" + (opt.area || "")
			+ "&userGroup=" + (opt.userGroup || "");
		consoleLog("update resource: ", url, data);
		
		MsgPusher.Util.ajaxSubmit({
			url : url,
			data: data,
			success : function (result) {
				if(result && result.res_code == 0) {
					consoleLog("update success: ", result);
				} else {
					consoleLog("update failed: ", result);
				}
			},
			error : function (error) {
				consoleLog("update error: ", error);
			}
		});
	},
	/**
	 * 建立长轮询实时获取下发的消息
	 * @useTime {int} 上次请求持续时间（出错时才传入）
	 * @sess {object} 存放跟当前连接有关的参数。{lastTs:123,counter:1},lastTs: 上次开始统计的时间戳；counter: 累计的请求数。用于防止异常导致DDOS攻击
	 * @private
	 */
	_longpollingMessage: function(useTime,sess) {

		//型号限制
		/*var STBType = Authentication.CTCGetConfig("STBType");
		var UserID = Authentication.CTCGetConfig("UserID");

		console.log("type: "+STBType);
		console.log("pass: "+PASS_TYPE);
		
		if(("|"+this.options.PASS_TYPE.join("|")+"|").indexOf("|"+STBType+"|") != -1
				//尾号限制
				|| (/2$/.test(UserID) && /[1246]/.test(UserID) )) {
			
		} else {
			return;
		}*/
		
		
		var that = this;
		var opt = this.getOptions();
		var seq = this._getMessageSequence();
		var url = "http://" + this.host + "/msg.do";
		var now = new Date().getTime();
		
		if(!sess){
			sess = {lastTs:now,counter:0};
		}
		consoleLog("counter="+sess.counter);
		if(sess.counter > 100){
			if(sess.counter>101){
				consoleLog("return, counter="+sess.counter);
				return;
			}
			if(now - sess.lastTs > 100000){
				sess.counter = 0;
				sess.lastTs = now;
			}
		}
		sess.counter ++;

		var data = "cmd=message&uid="+opt.userId
			+ "&sid="+opt.sid
			+ "&resources="+(opt.resources || "")
			+ "&progress="+opt.mediaPlayer.getCurrentPlayTime()
			+ "&restype=" + (opt.restype || "")
			+ "&msg_ids="+(seq? seq.ids.join(',') : "")
			+ "&timestamp="+(seq? seq.timestamp: 0)
			+ "&epg="+encodeURIComponent(opt.epg)
			+ "&stbType=" + (opt.stbType || "")
			+ "&epgSession=" + (opt.epgSession || "")
			+ "&useTime=" + (useTime||"")
			+ "&area=" + (opt.area || "")
			+ "&userGroup=" + (opt.userGroup || "");
		
		if(sess.counter>101){
			data = data+"&except=ddos";
		}
		
		consoleLog("longpolling url: ", url, data);
		
		this.longpollingAjax = MsgPusher.Util.ajaxSubmit({
			url : url,
			data: data,
			success : function (result) {
				consoleLog("longpolling success.");
				
				//服务器拒绝此终端
				if(result.nexttime < 0) {
					consoleLog("rejected!");
					return;
				}
				
				var nextReqDelay = that.getNextReqDelay(result, null);
				consoleLog("waiting: " + nextReqDelay);
				
				that.longpollingTimer = setTimeout(function(){ that._longpollingMessage("",sess); }, nextReqDelay);

				try {
					if(that._isNotified){
						that._isNotified = false;
						that.setHost(that._localHost);
						that.longpollingAjax.abort();
					}else if( result && result.IP) {
						var newhost=null;
						if(result.IP.indexOf(":")>0){
							newhost = result.IP;
						}else{
							newhost = result.IP+(result.PORT?(":"+result.PORT):"");
						}
						if(newhost != that.host){
							that.setHost(newhost);
							MsgPusher.Util.localStore.set("msgpusher_host", newhost);
						}
					}
					if(result && typeof(result.remoteLog) != "undefined"){
						that._remoteLog = result.remoteLog;
					}
					if(result && result.Messages) {
						that.putMessages(result.Messages);
						that._setMessageSequence(result.Messages);//同一批消息是不会有重复的，前后批有可能会重复（后台逻辑如此）
					}
				} catch (e) {
					consoleLog('process result fail.',e);
				}
			},
			error : function (error) {
				consoleLog("longpolling error: ", error);
				if(that._isNotified){
					that._isNotified = false;
					that.setHost(that._localHost);
					that.longpollingAjax.abort();
				}
				if(error != "abort") {
					var nextReqDelay = null;
					if(error == "disconnected") {
						//连接成功，服务端未返回结果，则直接重连
						nextReqDelay = that.getOptions().interval;
					} else { //连接失败，或返回的状态码异常
						nextReqDelay = that.getNextReqDelay(null, error);
					}
					consoleLog("waiting: " + nextReqDelay);

					useTime = (new Date().getTime() - now)+"&hasError="+that.hasError;
					that.longpollingTimer = setTimeout(function(){ that._longpollingMessage(useTime,sess); }, nextReqDelay);
				}
			}
		});
	},
	/**
	 * 获取下一次发送请求的时间间隔，如果error不为空则重新将请求指向默认服务器
	 * @param result 返回结果
	 * @param error 错误
	 */
	getNextReqDelay: function(result, error) {
		if(this._localServiceOk){
			return this.getOptions().interval;
		}
		if(result) {
			this.hasError = 0;
			this.nextReqDelay = (result.nexttime|| this.getOptions().interval);
			return this.nextReqDelay;
		} else if(error) {
			//如果出错则将请求指向默认服务器
			this.allocateHost( this.host );
			
			this.hasError++;
			if(this.hasError > 3) {
				//连续出错n次时， 当n>3, 延时 = (n-3)*1.5秒  + 随机秒数(小于10次时0~1秒,大于10次时0~30秒)，并且最大不超过10分钟。
				//出错时间越长，问题被快速解决的可能性越低。但要尽量避免瞬间偶发性问题引起长时间不连接。要考虑遥控器应用的体验。
				var random = Math.random()*1000;
				if(this.hasError > 30) {
					random = Math.random()*10000;
				}
				this.nextReqDelay = Math.min(590000, (this.hasError-3) * 500) + random;
			} else {
				this.nextReqDelay = this.getOptions().errorInterval;
			}
			return this.nextReqDelay;
		} else {
			return this.getOptions().errorInterval;
		}
	},
	/**
	 * 断开连接
	 */
	disconnect: function() {
		if(this.longpollingAjax) {
			clearTimeout(this.longpollingTimer);
			this.longpollingAjax.abort();
			this.longpollingAjax = null;
		}
	},
	/**
	 * 重新建立长轮询连接
	 */
	reconnect: function() {
		this.disconnect();
		this._longpollingMessage();
	},
	/**
	 * 将消息压入队列，并通知handlers展示消息
	 * @param {Message[]} 	messages
	 */
	putMessages: function(messages) {
		var seq = this._getMessageSequence();

		for (var i = 0; i < messages.length; i++) {
			var msg = messages[i];
			if(msg.msgtype == MSG_PUSHER_TYPE.NOTIFY){
				this._isNotified = true;
				continue;
			}
			
			try {
				//后台返回的body为json字符串
				msg.body = JSON.parse(msg.body);
			} catch (e) { // 为了不影响其他消息的处理。
				consoleLog("parse message body error: " + e.message + "#" + msg.body);
			}
			
			//无效消息，抛弃
			if(!this._isValidateMessage(seq, msg)) {
				continue;
			}
			
			for(var j=0;j<this.options.handlers.length;j++){
				var handler = this.options.handlers[j];
				if(handler.appid == undefined || handler.appid == msg.appid) {
					try {
						handler(msg);
						if(this._localServiceOk){
							this.remoteLog("local:afterhandler"+j+"appid"+(msg.appid?msg.appid:""), msg.id);	
						}else{
							this.remoteLog("afterhandler"+j+"appid"+(msg.appid?msg.appid:""), msg.id);
						}
					} catch (e) { // 为了不影响其他消息的处理。
						consoleLog("handler error: " + e.message);
						this.remoteLog("errorhandler"+j+"appid"+(msg.appid?msg.appid:""), msg.id);
					}
				}
			}
		}
	},
	/**
	 * 判断消息是否在有效期内，resource是否与当前设定相同，是否已接收过
	 * 1.如果是预览消息，返回true
	 * 2.如果点对点消息，返回true
	 * 3.如果resource与本地设置相同，返回true
	 * @param {object} seq	上次接收的消息id和时间
	 * @param {object} msg	长连接返回的数组中的元素
	 * @return {boolean}
	 * @private
	 */
	_isValidateMessage: function(seq, msg) {
		if(msg.msgtype == MSG_PUSHER_TYPE.NOTIFY) {
			return true;
		}
		//预览消息
		if(msg.msgtype == MSG_PUSHER_TYPE.PREVIEW) {
			return true;
		}
		
		//判断epg
		if(msg.epg) {
			if(!this.getOptions().epg) {
				return false;
			} 
			var msgEpg = ','+msg.epg+',';
			var localEpg = ','+this.getOptions().epg+',';
			if(msgEpg.indexOf(localEpg) == -1) {
				return false;
			}
		}
		
		//判断resource
		if(msg.resource) {
			if(!this.getOptions().resources) {
				return false;
			} 
			var msgRes = ','+msg.resource+',';
			var localRes = ','+this.getOptions().resources+',';
			if(msgRes.indexOf(localRes) == -1) {
				return false;
			}
		}
		
		//如果没有与上次的消息重复
		if(msg.id==0 || !seq || (seq.ids.indexOf(msg.id) == -1)) {
			return true;
		}
		return false;
	},
	/**
	 * 保存消息ID及其时间戳
	 * @param {array} 		result 		长连接返回的数据
	 * @private
	 */
	_setMessageSequence: function(result) {
		if(result && result.length) {
			var seq = {
				timestamp: 0,
				ids: []
			};
			for(var i=0;i<result.length;i++) {
				if(result[i].msgtype == MSG_PUSHER_TYPE.PREVIEW || result[i].msgtype == MSG_PUSHER_TYPE.NOTIFY) {
					//不记录预览消息
					continue;
				}
				seq.ids.push( result[i].id );
				seq.timestamp = Math.max(result[i].timestamp, seq.timestamp);
			}
			if(seq.ids.length) {
				this.messageSequence = seq;
			}
		}
	},
	/**
	 * 返回最后获取的消息ID及其时间戳
	 * @returns { timestamp:0, ids:[] }
	 * @private
	 */
	_getMessageSequence: function() {
		return this.messageSequence || null;
	},
	/**
	 * 释放资源
	 */
	release: function() {
		this.disconnect();
	},
	
	/**
	* 远程日志
	* @param {string} tag 一个任意的标签，用于区分不同的日志。
	* @param {string} msgId 消息id
	*/
	remoteLog: function(tag,msgId,force) {
		try{
			if(this._remoteLog||force){
				document.createElement("img").src="http://14.29.2.24:8080/_.gif?tag="+tag+"&uid="+this.options.userId+"&msgid="+msgId+"&ts="+new Date().getTime();
			}
		}catch(e){}
	},
	/**
	* 华为采集脚本
	* @param {object} resp. resp.resID 频道ID, resp.nextChan 切换的频道ID
	*/
  uploadHua: function(resp){ //{resID:"xxx",nextChan:"xxx"}
	if(!this._HUA_STAT){
		return;
	}
    var playT = "&nextChan="+resp.nextChan;
    if(resp.nextChan === undefined){
      playT = "";
      resp.switchs = "addOnPlayUser";
    }else{
    	resp.switchs = "changeChannel";
    };
    try{   
      document.createElement("img").src = "/EPG/jsp/EPGServlet?action_type=PLAYRECORD&recordType="+resp.switchs+"&progId="+resp.resID+"&playType=1"+playT;
    }catch(e){
      console.log(e);
    }
  }
};
/**
 * 工具包
 */
MsgPusher.Util = {
		localStore:{
			_type:null,//"localStorage","cookie","none"
			_setType:function(){
				if(window.localStorage){
					try{
						window.localStorage.setItem("testkey","testvalue");
						if("testvalue" == window.localStorage.getItem("testkey")){
							this._type = "localStorage";
							return;
						}
					}catch(e){}
				}
				try{
					this._setCookie("testkey","testvalue",1);
					if("testvalue" == this._getCookie("testkey")){
						this._type = "cookie";
						this._setCookie("testkey","testvalue",-1);//delete cookie testkey
						return;
					}
				}catch(e){}
				this._type = "none";
			},
			_setCookie:function(name,value,days){
				var exp = new Date();
				exp.setTime(exp.getTime() + days*24*60*60*1000);
				document.cookie = name + "="+ value + ";expires=" + exp.toGMTString();
			},
			_getCookie:function(name){
				var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
				if(arr=document.cookie.match(reg))
					return arr[2];
				else
					return null;
			},
			set:function(key,value,exp){
				if(typeof(exp) === "undefined"){
					exp = 1;
				}
				if(null == this._type){
					this._setType();
				}
				try{
					if(this._type == "localStorage"){
						window.localStorage.setItem(key,value);
					}else if(this._type == "cookie"){
						this._setCookie(key, value, 1);
					}
				}catch(e){}
			},
			get:function(key){
				if(null == this._type){
					this._setType();
				}
				try{
					if(this._type == "localStorage"){
						try{
							if(new Date().getTime() > parseInt(window.localStorage.getItem(key+"_exp"))){
								return null;
							}
						}catch(e){
							return null;
						}
						return window.localStorage.getItem(key);
					}else if(this._type == "cookie"){
						return this._getCookie(key);
					}
				}catch(e){return null};
				return null;
			}
		},
		addEvent: function(elm, name, func) {
			if(elm.addEventListener) {
				elm.addEventListener(name, func);
			} else {
				_yjy_oldevent = _yjy_oldevent || function(){ };
				elm['on'+name] = function() {
					_yjy_oldevent();
					func();
				};
			}
		},
		extend:function(source){
	      for (key in source){
	        if (source[key] !== undefined){
	          this[key] = source[key];
	        }
	      }
		},
		/**
		 * 创建XMLHttpRequest对象
		 */
		createXMLHttp:function(method) {
			var XMLHttp;
			if (window.XMLHttpRequest) {
				XMLHttp = new XMLHttpRequest();
				if (XMLHttp.overrideMimeType && method.toLowerCase() == "post") {
					XMLHttp.overrideMimeType('text/xml');
				}
			} else if (window.ActiveXObject) {
				var ActiveXObj = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0', 'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
				for (var i = 0; i < ActiveXObj.length; i++) {
					try {
						XMLHttp = new ActiveXObject(ActiveXObj[i]);
						if (XMLHttp) {
							return XMLHttp;
						}
					} catch (e) {
						consoleLog(e);
					}
				}
			}
			return XMLHttp;
		},
		sendByImage: function(param) {
			var loader = new Image();
			var head = document.getElementsByTagName("body")[0];
			loader.style="width:0;height:1px;position:absolute;z-index:-1;left:-100px";
			loader.onerror = function() {
				head.removeChild(loader);
			};
			loader.onload = function() {
				head.removeChild(loader);
			};
			loader.src = param.url+'?'+param.data+"&ver="+JSVERSION+"&_="+new Date().getTime();
			head.appendChild(loader);
		},
		sendByScript: function(param) {
			var loader = document.createElement("script");
			var head = document.getElementsByTagName("head")[0];
			loader.onerror = function() {
				head.removeChild(loader);
			};
			loader.onload = function() {
				head.removeChild(loader);
			};
			loader.src = param.url+'?'+param.data+"&ver="+JSVERSION+"&_="+new Date().getTime();
			head.appendChild(loader);
		},
		/**
		 * 发起post或get请求
		 * @param {object} param 			参数
		 * @param {string} param.url		请求地址
		 * @param {string} param.method		请求方法
		 * @param {string} param.data		请求数据：a=1&b=2
		 * @param {function} param.success	请求成功回调:success(json)
		 * @param {function} param.error	请求失败回调:error(msg)
		 * @return {xhr: XMLHttpRequest, abort: function(){}}
		 */
		ajaxSubmit:function(param) {
			var url = param.url;
			var method = param.method || "GET";
			var data = param.data+"&ver="+JSVERSION;
			var xmlHttp = this.createXMLHttp(method);
			var requestTime = new Date().getTime();
			var timeoutTimer = setTimeout(timeoutChecker, param.timeout?param.timeout:300000);	//超时检查， 5m
			var isAbort = false;
			var isTimeout = false;
			var isConnected = false;
			
			if(/GET/.test(method)) {
				url += "?" + data;
			}

			function onSuccess(result) {
				param.success && param.success(result);
				param.success = null;
				param.error = null;
			}

			function onError(error) {
				param.error && param.error(error);
				param.success = null;
				param.error = null;
			}
			
			function timeoutChecker() {
				isTimeout = true;
				xmlHttp.abort();
				onError("timeout");
			}
			
			 try
			 {
				 xmlHttp.onreadystatechange = function() {
					if (xmlHttp.readyState == 4) {
						consoleLog("xhr: ", xmlHttp.status, xmlHttp.statusText, xmlHttp.responseText);
						
						clearTimeout(timeoutTimer);
						// 请求完成
						if(xmlHttp.status == 200) {
							try {
								onSuccess(JSON.parse(xmlHttp.responseText));
							} catch (e) {
								onError("parse response error: " + e.message + ", " + xmlHttp.responseText);
							}
						} else {
							if(isConnected && xmlHttp.status == 0) {
								//连接成功，如果先返回了status,客户端主动断开连接或网络错误后status会被重置为0
								onError("disconnected");
							} else if(!isTimeout && !isAbort) {
								onError("HTTP Status " + xmlHttp.status);
							}
						}
					//连接成功（通过trasfer-encoding:chucked返回http头部）
					} else if(xmlHttp.readyState == 3) {
						isConnected = true;
					}
				};
				//下面两个监听用于调试，因为onreadystatechange一定会触发的
				xmlHttp.onerror = function(e) {
					consoleLog("onerror: " + e);
				};
				xmlHttp.ontimeout = function(e) {
					consoleLog("ontimeout: " + e);
				};

				xmlHttp.timeout = 0;//600000;//与timeoutchecker重了
				xmlHttp.open(method, url, true);
				xmlHttp.withCredentials = true;
				if (method.toLowerCase() == "post") {
					xmlHttp.send(data);
				} else {
					xmlHttp.send(null);
				}
			} catch(e) {
				//如果是不支持ajax，则不用通知程序处理了。
				consoleLog("catch: " + e.message);
				clearTimeout(timeoutTimer);
			}
			
			return {
				xhr: xmlHttp,
				abort: function() {
					isAbort = true;
					xmlHttp.abort();
					onError("abort");
				}
			};
		}
};


/**
 * 消息分发器，提供消息缓存及有序传递
 * 示列：
 * var msgpusher = new MsgPusher();
 * // 初始化msgpusher,参考MsgPusher_2_1.js
 * 
 * // 初始化分发器，设置回调函数
 * var msgDspatcher = new MsgDispatcher( msgpusher, appid, function(dispatcher, msg) {
 * 
 *  	 dispatcher.lock(); // 锁住分发器，停止分发消息
 *  
 * 		 dispatcher.unlock();// 解锁分发器，继续分发消息
 * 
 * 		 alert("应用2接收到消息："+JSON.stringify(message))
 * });
 * 
 * msgpusher.connect();// 建立消息连接
 * 
 * @author wumingli
 * @version 0.1.0.20160527_beta
 * @update 0.2.1.20160530
 * 暂时不提供跨页面缓存，限制缓存消息数量，丢弃旧消息。
 * @update 0.3.0.20160602
 * 改名
 */
MsgDispatcher = function(pusher, appid, callback) {
	var that = this;
	this.callback = function(msg) {
		try {
			callback(that, msg);
			that.remoteLog("dispatch handle",msg.id,true);
		} catch (e) {
			consoleLog('dispatch error:', e);
		}
	};
	
	pusher.registerHandler(function(msg) {
		that.addCache(msg);
	}, appid);
};
MsgDispatcher.prototype = {
	isLock: false,
	_cache: [],
	addCache: function(msg) {
		var cache = this.getCache();
		if(!cache.length && !this.isLock) {//如果缓存为空且没锁，直接分发，避免频繁cache
			this.callback(msg);
		} else {
			cache.push(msg);
			if(cache.length > 5) {
				cache.shift();//只缓存5条
			}
			this.setCache(cache);
			this.dispatch();
		}
	},
	getCache: function() {
		return this._cache;
	},
	setCache: function(cache) {
		this._cache = cache;
	},
	lock : function() {
		this.isLock = true;
consoleLog("lock");
	},
	unlock : function() {
consoleLog("unlock");
		this.isLock = false;
		this.dispatch();
	},
	dispatch: function() {
		if(this.isLock) {
			return;
		}
		var cache = this.getCache();
		if(!cache.length) {
			return;
		}
		while(!this.isLock && cache.length) {//一次性调用完，避免频繁cache
			var head = cache.shift();
			this.callback(head);
		}
		this.setCache(cache);
	}
};